namespace tb
{

	// ================================
	// Constructors
	// ================================

	TB_INLINE Vec2::Vec2() 
	{ 

	}

	TB_INLINE Vec2::Vec2(float a_X, float a_Y)
	{
		x = a_X;
		y = a_Y;
	}

	TB_INLINE Vec2::Vec2(float* a_Vector)
	{ 
		x = a_Vector[0];
		y = a_Vector[1];
	}

	TB_INLINE Vec2::Vec2(const Vec2& a_Copy)
	{
		x = a_Copy.x;
		y = a_Copy.y;
	}

	TB_INLINE Vec2::~Vec2()
	{

	}

	// ================================
	// Conversion
	// ================================

	TB_INLINE Vec2& Vec2::FromAngle(float a_Degrees)
	{
		x = Math::CosDeg(a_Degrees);
		y = Math::SinDeg(a_Degrees);

		return *this;
	}

	TB_INLINE Vec2& Vec2::FromPolarCoordinates(float a_Degrees, float a_Length)
	{
		x = Math::CosDeg(a_Degrees) * a_Length;
		y = Math::SinDeg(a_Degrees) * a_Length;

		return *this;
	}

	TB_INLINE Vec2& Vec2::ToPolarCoordinates(float& a_Length, float& a_Radians)
	{
		a_Length = Math::Sqrt(x * x + y * y);
		a_Radians = Math::ArcTan2(y, x);

		return *this;
	}

	TB_INLINE Vec2& Vec2::ToOpenGLVector(float* a_Target)
	{
		a_Target[0] = x;
		a_Target[1] = y;

		return *this;
	}

	// ================================
	// Assignment & Access
	// ================================

	TB_INLINE Vec2& Vec2::operator = (const Vec2& a_Other)
	{
		x = a_Other.x;
		y = a_Other.y;
		return *this;
	}
	TB_INLINE Vec2& Vec2::operator = (float a_Value)
	{
		x = y = a_Value;
		return *this;
	}

	TB_INLINE float& Vec2::operator [] (int a_Member)
	{
		return (&x)[a_Member];
	}

	TB_INLINE float Vec2::operator [] (int a_Member) const
	{
		return (&x)[a_Member];
	}

	// ================================
	// Operators
	// ================================

	TB_INLINE Vec2 Vec2::operator - () const
	{
		return Vec2(-x, -y);
	}

	TB_INLINE Vec2& Vec2::operator += (const Vec2& a_Other)
	{ 
		x += a_Other.x;
		y += a_Other.y;
		return *this;
	}
	TB_INLINE Vec2 Vec2::operator + (const Vec2& a_Other) const
	{ 
		Vec2 result(x + a_Other.x, y + a_Other.y);
		return result;
	}

	TB_INLINE Vec2& Vec2::operator -= (const Vec2& a_Other)
	{ 
		x -= a_Other.x;
		y -= a_Other.y;
		return *this;
	}
	TB_INLINE Vec2 Vec2::operator - (const Vec2& a_Other) const
	{
		Vec2 result(x - a_Other.x, y - a_Other.y);
		return result;
	}

	TB_INLINE Vec2& Vec2::operator *= (float a_Scalar)			
	{ 
		x *= a_Scalar;
		y *= a_Scalar;
		return *this;
	}
	TB_INLINE Vec2& Vec2::operator *= (const Vec2& a_Other)		
	{ 
		x *= a_Other.x;
		y *= a_Other.y;
		return *this;
	}
	TB_INLINE Vec2 Vec2::operator * (float a_Value) const
	{ 
		Vec2 result(x * a_Value, y * a_Value);
		return result;
	}
	TB_INLINE Vec2 Vec2::operator * (const Vec2& a_Other) const
	{ 
		Vec2 result(x * a_Other.x, y * a_Other.y);
		return result;
	}

	TB_INLINE Vec2& Vec2::operator /= (float a_Value)				
	{ 
		const float inv = 1 / a_Value;
		x *= inv;
		y *= inv;
		return *this;
	}
	TB_INLINE Vec2& Vec2::operator /= (const Vec2& a_Other)		
	{ 
		x /= a_Other.x;
		x /= a_Other.y;
		return *this;
	}
	TB_INLINE Vec2 Vec2::operator / (float a_Value) const
	{ 
		const float inv = 1.f / a_Value;
		Vec2 result(x * inv, y * inv);
		return result;
	}
	TB_INLINE Vec2 Vec2::operator / (const Vec2& a_Other) const
	{ 
		Vec2 result(x / a_Other.x, y / a_Other.y);
		return result;
	}

	// ================================
	// Comparisons
	// ================================

	TB_INLINE bool Vec2::operator == (const Vec2& a_Point) const
	{			
		if (x != a_Point.x || y != a_Point.y) { return false; }
		return true;
	}
	TB_INLINE bool Vec2::operator == (float a_Value) const
	{
		if (x != a_Value || y != a_Value) { return false; }
		return true;
	}

	TB_INLINE bool Vec2::operator != (const Vec2& a_Point) const
	{
		if (x == a_Point.x || y == a_Point.y) { return false; }
		return true;
	}
	TB_INLINE bool Vec2::operator != (float a_Value) const
	{
		if (x == a_Value || y == a_Value) { return false; }
		return true;
	}

	TB_INLINE bool Vec2::operator > (const Vec2& a_Point) const
	{
		if (x <= a_Point.x || y <= a_Point.y) { return false; }
		return true;
	}
	TB_INLINE bool Vec2::operator > (float a_Value) const
	{
		if (x <= a_Value || y <= a_Value) { return false; }
		return true;
	}

	TB_INLINE bool Vec2::operator >= (const Vec2& a_Point) const
	{
		if (x < a_Point.x || y < a_Point.y) { return false; }
		return true;
	}
	TB_INLINE bool Vec2::operator >= (float a_Value) const
	{
		if (x < a_Value || y < a_Value) { return false; }
		return true;
	}

	TB_INLINE bool Vec2::operator < (const Vec2& a_Point) const
	{
		if (x >= a_Point.x || y >= a_Point.y) { return false; }
		return true;
	}
	TB_INLINE bool Vec2::operator < (float a_Value) const
	{
		if (x >= a_Value || y >= a_Value) { return false; }
		return true;
	}

	TB_INLINE bool Vec2::operator <= (const Vec2& a_Point) const
	{
		if (x < a_Point.x || y < a_Point.y) { return false; }
		return true;
	}
	TB_INLINE bool Vec2::operator <= (float a_Value) const
	{
		if (x > a_Value || y > a_Value) { return false; }
		return true;
	}

	TB_INLINE bool Vec2::IsEqual(const Vec2& a_Other, float a_Delta) const
	{
		if (x < a_Other.x - a_Delta || x > a_Other.x + a_Delta) { return false; }
		if (y < a_Other.y - a_Delta || y > a_Other.y + a_Delta) { return false; }
		return true;
	}

	TB_INLINE bool Vec2::IsEqual(float a_Value, float a_Delta) const
	{
		if (x < a_Value - a_Delta || x > a_Value + a_Delta) { return false; }
		if (y < a_Value - a_Delta || y > a_Value + a_Delta) { return false; }
		return true;
	}

	// ================================
	// Functions
	// ================================

	TB_INLINE Vec2& Vec2::Clear()
	{
		x = y = 0.f;
		return *this;
	}

	TB_INLINE Vec2& Vec2::Reverse()
	{
		x *= -1.f;
		y *= -1.f;

		return *this;
	}

	TB_INLINE Vec2& Vec2::PerpendicularLeft()
	{
		float oldx = x;
		x = -y;
		y = oldx;

		return *this;
	}

	TB_INLINE Vec2 Vec2::GetPerpendicularLeft() const
	{
		return Vec2(-y, x);
	}

	TB_INLINE Vec2& Vec2::PerpendicularRight()
	{
		float oldy = y;
		y = -x;
		x = oldy;

		return *this;
	}

	TB_INLINE Vec2 Vec2::GetPerpendicularRight() const
	{
		return Vec2(y, -x);
	}

	TB_INLINE Vec2& Vec2::Reciprocral()
	{
		x = 1 / x;
		y = 1 / y;

		return *this;
	}

	TB_INLINE Vec2& Vec2::Normalize()
	{
		const float length = 1.f / Math::Sqrt(x * x + y * y);
		x *= length;
		y *= length;

		return *this;
	}

	TB_INLINE float Vec2::GetSquaredLength() const
	{
		return ((x * x) + (y * y));
	}

	TB_INLINE float Vec2::GetLength() const
	{
		return (Math::Sqrt((x * x) + (y * y)));
	}

	TB_INLINE Vec2 Vec2::GetNormalized() const
	{
		const float length = 1.f / Math::Sqrt((x * x) + (y * y));
		return Vec2(x * length, y * length);
	}

	TB_INLINE Vec2 Vec2::GetReciprocal() const
	{
		return Vec2(1.f / x, 1.f / y);
	}

	TB_INLINE Vec2 Vec2::GetReversed() const
	{
		return Vec2(-x, -y);
	}

	TB_INLINE float Vec2::GetDotProduct(const Vec2& a_Other) const
	{
		return (
			(x * a_Other.x) +
			(y * a_Other.y)
		);
	}

	TB_INLINE Vec2& Vec2::DirectionTo(const Vec2& a_Other)
	{
		float dx = a_Other.x - x;
		float dy = a_Other.y - y;
		float rcp_len = Math::InvSqrt((dx * dx) + (dy * dy));

		x = dx * rcp_len;
		y = dy * rcp_len;

		return *this;
	}


	TB_INLINE Vec2 Vec2::GetDirectionTo(const Vec2& a_Other) const
	{
		float dx = a_Other.x - x;
		float dy = a_Other.y - y;
		float rcp_len = Math::InvSqrt((dx * dx) + (dy * dy));

		return Vec2(
			dx * rcp_len,
			dy * rcp_len
		);
	}


	TB_INLINE float Vec2::GetSquaredDistanceBetween(const Vec2& a_Other) const
	{
		const float diffX = x - a_Other.x;
		const float diffY = y - a_Other.y;

		return ((diffX * diffX) + (diffY * diffY));
	}

	TB_INLINE float Vec2::GetDistanceBetween(const Vec2& a_Other) const
	{
		const float diffX = x - a_Other.x;
		const float diffY = y - a_Other.y;

		return (Math::Sqrt((diffX * diffX) + (diffY * diffY)));
	}

	TB_INLINE float Vec2::GetAngle() const
	{
		return Math::RadToDeg(Math::ArcTan2(y, x));
	}

	TB_INLINE float Vec2::GetAngleBetween(const Vec2& a_Other) const
	{
		return Math::RadToDeg(Math::ArcTan2(y - a_Other.y, x - a_Other.x));
	}

	// ================================
	// Interpolation
	// ================================

	TB_INLINE Vec2& Vec2::Lerp(const Vec2& a_Other, float a_Percentage)
	{
		x = (a_Percentage * x) + ((1.f - a_Percentage) * a_Other.x);
		y = (a_Percentage * y) + ((1.f - a_Percentage) * a_Other.y);

		return *this;
	}

	TB_INLINE Vec2 Vec2::GetLerp(const Vec2& a_Other, float a_Percentage) const
	{
		Vec2 result(
			(a_Percentage * x) + ((1.f - a_Percentage) * a_Other.x),
			(a_Percentage * y) + ((1.f - a_Percentage) * a_Other.y)
		);

		return result;
	}

	TB_INLINE Vec2& Vec2::Slerp(const Vec2& a_Other, float a_Percentage)
	{
		float cosom = (x * a_Other.x) + (y * a_Other.y);

		if (cosom < 0.f)
		{
			cosom = -cosom;
		}

		float scale_src, scale_dst;

		if (1.f - cosom > 0.001f)
		{
			float omega = tb::Math::ArcCos(cosom);
			float rcp_sinom = 1.f / tb::Math::Sin(omega);
			scale_src = tb::Math::Sin((1.f - a_Percentage) * omega) * rcp_sinom;
			scale_dst = tb::Math::Sin((      a_Percentage) * omega) * rcp_sinom;
		}
		// use linear interpolation if too close together
		else
		{
			scale_src = 1.f - a_Percentage;
			scale_dst = a_Percentage;
		}

		x = (scale_src * x) + (scale_dst * a_Other.x);
		y = (scale_src * y) + (scale_dst * a_Other.y);
			
		return *this;
	}

	TB_INLINE Vec2 Vec2::GetSlerp(const Vec2& a_Other, float a_Percentage) const
	{
		float cosom = (x * a_Other.x) + (y * a_Other.y);

		if (cosom < 0.f)
		{
			cosom = -cosom;
		}

		float scale_src, scale_dst;

		if (1.f - cosom > 0.001f)
		{
			float omega = tb::Math::ArcCos(cosom);
			float rcp_sinom = 1.f / tb::Math::Sin(omega);
			scale_src = tb::Math::Sin((1.f - a_Percentage) * omega) * rcp_sinom;
			scale_dst = tb::Math::Sin((      a_Percentage) * omega) * rcp_sinom;
		}
		// use linear interpolation if too close together
		else
		{
			scale_src = 1.f - a_Percentage;
			scale_dst = a_Percentage;
		}

		return Vec2(
			(scale_src * x) + (scale_dst * a_Other.x),
			(scale_src * y) + (scale_dst * a_Other.y)
		);
	}

	TB_INLINE Vec2& Vec2::SlerpNormalized(const Vec2& a_Other, float a_Percentage)
	{
		Normalize();

		float rcp_blen = a_Other.GetLength();
		tb::Vec2 b(a_Other.x * rcp_blen, a_Other.y * rcp_blen);

		return Slerp(b, a_Percentage);
	}

	TB_INLINE Vec2 Vec2::GetSlerpNormalized(const Vec2& a_Other, float a_Percentage) const
	{
		float rcp_alen = GetLength();
		tb::Vec2 a(x * rcp_alen, y * rcp_alen);

		float rcp_blen = a_Other.GetLength();
		tb::Vec2 b(a_Other.x * rcp_blen, a_Other.y * rcp_blen);

		return a.Slerp(b, a_Percentage);
	}

}